/**
 * @Class Request
 * @description http请求封装
 * 本插件借鉴了luch-request和axios
 * luch-request 详见：https://www.quanzhan.co/luch-request/
 * axios 详见：http://www.axios-js.com/
 */
import { isObj, deepClone, empty } from "../../utils";
import { requestConfig } from "../config";
import type {
	RequestConfig,
	RequestOption,
	UpLoadConfig,
	DownLoadConfig,
	RequestExample,
	DispatchRequest,
	CustomResponse,
	PartialRequestConfig,
} from "../types";
import InterceptorManager from "./InterceptorManager";
import mergeConfig from "./mergeConfig";
import dispatchRequest from "./dispatchRequest";

const transformUrl = (url : string, obj : Record<string, any>) => {
	let params = Object.keys(obj)
		.map(function (k) {
			return k + '=' + obj[k]
		})
		.join('&')
	return `${url}?${params}`
}

export default class Request<T = Record<string, any>, C extends Record<string, any> = Record<string, any>> {
	/**
	 * @Class Request接受一个泛型，来作为interceptors.response拦截器中的每个函数的参数中的data变量的类型
	 * @description for example ：<br>
	 * const request = new Request<{code:number}>() <br>
	 * request.interceptors.response.use((response)=> response) <br>
	 * 此处的response参数中，response.data就是传入的泛型类型。即{code:number}
	 *
	 * @param {Object} opt - 全局配置
	 * @param {String} opt.baseURL - 全局根路径
	 * @param {Object} opt.header - 全局header
	 * @param {String} opt.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
	 * @param {String} opt.dataType = [json] - 全局默认的dataType
	 * @param {String} opt.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持
	 * @param {Object} opt.custom - 全局默认的自定义参数
	 * @param {Number} opt.timeout - 全局默认的超时时间，单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序（2.10.0）、支付宝小程序
	 * @param {Boolean} opt.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持（HBuilderX 2.3.3+）
	 * @param {Boolean} opt.withCredentials - 全局默认的跨域请求时是否携带凭证（cookies）。默认false。仅H5支持（HBuilderX 2.6.15+）
	 * @param {Boolean} opt.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)
	 * @param {Function(statusCode):Boolean} opt.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
	 */
	constructor(opt : Partial<RequestConfig<C>> = {}) {
		if (!isObj(opt)) {
			opt = {};
			console.warn("设置全局参数必须接收一个Object");
		}
		this.config = deepClone(
			opt,
			requestConfig

		) as RequestConfig<C>;
	}
	config : RequestConfig<C>;
	/**
	 * @property {Function} request 请求拦截器
	 * @property {Function} response 响应拦截器
	 */
	interceptors = {
		request: new InterceptorManager<RequestConfig<C> & { data ?: Record<any, any> | Array<any> | string }>(),
		response: new InterceptorManager<CustomResponse<T, C>>(),
	};
	/**
	 * @Function
	 * @param {Request~setConfigCallback}  设置全局默认配置
	 */
	setConfig(f : (config : RequestConfig<C>) => RequestConfig<C>) {
		this.config = f(this.config);
	}
	/**
	 * request中间件
	 */
	middleware<D = T>(config : PartialRequestConfig) {
		config = mergeConfig(this.config, config);
		const chain : Array<
			| RequestExample<RequestConfig<C>>
			| RequestExample<CustomResponse<T,C>>
			| DispatchRequest
			| undefined
		> = [dispatchRequest, undefined];
		let promise : any = Promise.resolve(config);
		for (let interceptor of this.interceptors.request) {
			chain.unshift(interceptor!.fulfilled, interceptor!.rejected);
		}

		for (let interceptor of this.interceptors.response) {
			chain.push(interceptor!.fulfilled, interceptor!.rejected);
		}

		while (chain.length) {
			promise = promise.then(chain.shift(), chain.shift());
		}

		return promise as Promise<D>;
	}
	/**
	 * @Function
	 * @param {Object} config - 请求配置项
	 * @prop {Object} config.params - 请求参数
	 * @prop {Object} [config.responseType] [text|arraybuffer] - 响应的数据类型
	 * @prop {Object} [config.dataType] - 如果设为 json，会尝试对返回的数据做一次 JSON.parse
	 * @prop {Object} [config.header] - 请求header
	 */
	request<D = T>(config : PartialRequestConfig = {}) {
		return this.config.modifyData(this.middleware<D>(config), config);
	}

	get<D = T>(url : string, options : Omit<RequestOption<C>, 'data'> & { data ?: Record<string, any> } = {}) {
		const option = { ...options }
		if (empty(option.data)) {
			option.data = {}
		}
		if (empty(option.params)) {
			option.params = {}
		}
		option.data = deepClone(
			option.params,
			option.data
		);
		return this.request<D>({
			url,
			method: "GET",
			...options,
		});
	}

	post<D = T>(url : string, options : RequestOption<C> = {}) {
		if (isObj(options.params)) {
			url = transformUrl(url, options.params)
			options.params = {}
		}
		return this.request<D>({
			url,
			method: "POST",
			...options,
		});
	}

	// #ifndef MP-ALIPAY
	put<D = T>(url : string, options : RequestOption<C> = {}) {
		if (isObj(options.params)) {
			url = transformUrl(url, options.params)
		}
		return this.request<D>({
			url,
			method: "PUT",
			...options,
		});
	}

	// #endif

	// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
	delete<D = T>(url : string, options : RequestOption<C> = {}) {
		if (isObj(options.params)) {
			url = transformUrl(url, options.params)
		}
		return this.request<D>({
			url,
			method: "DELETE",
			...options,
		});

	}

	// #endif

	// #ifdef H5 || MP-WEIXIN
	connect<D = T>(url : string, options : RequestOption<C> = {}) {
		return this.request<D>({
			url,
			method: "CONNECT",
			...options,
		});
	}

	// #endif

	// #ifdef  H5 || MP-WEIXIN || MP-BAIDU
	head<D = T>(url : string, options : RequestOption<C> = {}) {
		return this.request<D>({
			url,
			method: "HEAD",
			...options,
		});

	}

	// #endif

	// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
	options<D = T>(url : string, options : RequestOption<C> = {}) {
		return this.request<D>({
			url,
			method: "OPTIONS",
			...options,
		});
	}

	// #endif

	// #ifdef H5 || MP-WEIXIN
	trace<D = T>(url : string, options : RequestOption<C> = {}) {
		return this.request<D>({
			url,
			method: "TRACE",
			...options,
		});
	}

	// #endif

	upload<D = T>(url : string, options : UpLoadConfig<C> = {}) {
		return this.request<D>({
			url,
			method: "UPLOAD",
			...options,
		});

	}

	download<D = T>(url : string, options : DownLoadConfig = {}) {
		return this.request<D>({
			url,
			method: "DOWNLOAD",
			...options,
		});
	}
}